package git.soulbgm.common.aop;

import git.soulbgm.common.annotation.ParamCheck;
import git.soulbgm.common.enumerate.ParamCheckLevel;
import git.soulbgm.common.exception.ParamIsNullException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author SoulBGM
 * @version 1.0
 * @date 2019/9/23 16:54
 */
@Component
@Aspect
@Slf4j
public class ParamCheckAop {

    /**
     * 定义有一个切入点，范围为web包下的类
     */
    @Pointcut("execution(public * git.soulbgm.controller.*.*(..))")
    public void checkParam() {
    }

    @Around("checkParam()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 获得目标bean的class
        Class<?> clazz = pjp.getTarget().getClass();
        // 获得目标方法名称
        String methodName = pjp.getSignature().getName();
        MethodSignature signature = ((MethodSignature) pjp.getSignature());
        //得到拦截的方法
        Method method = signature.getMethod();
        //获取方法参数注解，返回二维数组是因为某些参数可能存在多个注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) {
            return pjp.proceed();
        }
        //获取方法参数名
        String[] paramNames = signature.getParameterNames();
        //获取参数值
        Object[] paramValues = pjp.getArgs();
        //获取方法参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        Map<String, List<String>> errorMap = new HashMap<>(paramValues.length);
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                if (parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck) {
                    ParamCheck paramCheck = (ParamCheck) parameterAnnotations[i][j];
                    ParamCheckLevel[] levelArrays = paramCheck.level();
                    String[] valueArrays = paramCheck.value();
                    if (levelArrays.length != valueArrays.length && levelArrays.length == 1) {
                        ParamCheckLevel paramCheckLevel = levelArrays[0];
                        levelArrays = new ParamCheckLevel[valueArrays.length];
                        for (int p = 0; p < levelArrays.length; p++) {
                            levelArrays[p] = paramCheckLevel;
                        }
                    }

                    if (valueArrays.length == 1 && "".equals(valueArrays[0])) {
                        String parameterType = parameterTypes[i] == null ? null : parameterTypes[i].getName();
                        boolean flag = paramIsNull(paramNames[i], paramValues[i], parameterType, levelArrays[0]);
                        if (flag) {
                            errorMap.put(paramNames[i], Arrays.asList(parameterType + ":" + paramNames[i]));
                        }
                    } else {
                        for (int l = 0; l < valueArrays.length; l++) {
                            String paramName = valueArrays[l];
                            ParamCheckLevel level = levelArrays[l];
                            Object pojo = paramValues[i];
                            Class<?> pojoClass = pojo.getClass();
                            Field field = null;
                            try {
                                field = pojoClass.getDeclaredField(paramName);
                            } catch (NoSuchFieldException e) {
                                throw new RuntimeException("后端ParamCheck注解参数有误", e);
                            }
                            Object paramValue = executeGetMethod(pojo, field);
                            String parameterType = paramValue == null ? field.getType().getSimpleName() : paramValue.getClass().getSimpleName();
                            boolean flag = paramIsNull(paramName, paramValue, parameterType, level);
                            if (flag) {
                                List<String> errorList = errorMap.get(paramNames[i]);
                                if (errorList == null) {
                                    errorList = new ArrayList<>(5);
                                    errorMap.put(paramNames[i], errorList);
                                }
                                errorList.add(parameterType + ":" + paramName);
                            }
                        }
                    }
                    break;
                }
            }
        }
        if (errorMap.size() > 0) {
            throw new ParamIsNullException(errorMap);
        }
        return pjp.proceed();
    }

    /**
     * 参数非空校验，如果参数为空，则抛出ParamIsNullException异常
     *
     * @param paramName     参数名称
     * @param value         参数值
     * @param parameterType 参数类型
     */
    private boolean paramIsNull(String paramName, Object value, String parameterType, ParamCheckLevel checkLevel) {
        boolean flag = false;
        if (value == null) {
            flag = true;
        }
        if (checkLevel == ParamCheckLevel.NOT_EMPTY) {
            if (!flag && "".equals(value.toString())) {
                flag = true;
            }
        }
        return flag;
    }

    /**
     * 通过反射的方式执行getXxx()方法
     *
     * @param obj   实体对象
     * @param field 字段
     * @return 执行了getXxx()方法之后的值
     */
    public static Object executeGetMethod(Object obj, Field field) {
        if (obj == null) {
            return null;
        }
        try {
            return obj.getClass().getMethod(buildGetMethod(field.getName(), field.getType() == Boolean.class)).invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            return null;
        }
    }

    /**
     * 通过给字段名称构建出相应的get方法名称
     *
     * @param fieldName 字段名称
     * @return 组合之后的名称
     */
    public static String buildGetMethod(String fieldName, boolean isBoolean) {
        StringBuilder sb = new StringBuilder();
        if (isBoolean) {
            sb.append("is");
        } else {
            sb.append("get");
        }
        sb.append(Character.toUpperCase(fieldName.charAt(0)));
        sb.append(fieldName.substring(1));
        return sb.toString();
    }


}
